/*
	Copyright (c) 2008, Grant Jones <grant@podtronics.com>.
	All rights reserved.

	Redistribution and use in source and binary forms, with or
	without modification, are permitted provided that the following
	conditions are met:

		Redistributions of source code must retain the above copyright
		notice, this list of conditions and the following disclaimer.
		
		Redistributions in binary form must reproduce the above copyright
		notice, this list of conditions and the following disclaimer in
		the documentation and/or other materials provided with the
		distribution.
		
		Neither the name of the gxjones.com nor the names of its
		contributors may be used to endorse or promote products derived
		from this software without specific prior written permission.
	
	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
	"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
	LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
	FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
	COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
	INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
	(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
	SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
	HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
	STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
	OF THE POSSIBILITY OF SUCH DAMAGE.
	
	http://www.opensource.org/licenses/bsd-license.php
*/

/*
 *  edb_net_shell.c
 *  scatterd
 *
 *  Created by Grant Jones on 2/24/08.
 *
 */

#include "edb_main.h"
#include "edb_net_shell.h"

#include "edb_msg_parser.h"


void print_len( const char *str, int len )
{
	int x;
	for( x= 0; x < len; x ++ )
		printf("%c", str[x] );
}

// serializes a request based on the individual parts; call cmap_str_join(resp_str, &len_out); to get the resulting string (or send the parts of the string individually through the pipe)
cmapStruct *edb_serialize_req( mempool_t *pool, char *preamble, cmapStruct *items )
{
	cmapStruct *resp_str= cmap_new(pool);
	int x;
//"PREAMBLE WITH PARAMETERS AS $parameter1, $parameter2;\r\n
//<item count>\r\n
//<key_1_name> <bytes>, <key_2_name> <bytes>\r\n
//<key_1_data>\r\n
//<key_2_data>\r\n"
	
	cmap_str_append_bytes(resp_str, preamble, strlen(preamble) );
	cmap_str_concat(resp_str, "\r\n%ld\r\n", items->num );
	
	cmapEnumStruct e;
	CMAP_KEY_TYPE key;
	edb_net_item_t *item;
	cmap_enum( items, &e );
	while( (item = cmap_enum_next( &e, &key )) )
	{	
		// add attribute definition header:
		for( x = 0; x < item->attr_count; x ++ )
			cmap_str_concat(resp_str, "%s %ld%s", item->attrs[x].key, item->attrs[x].value_sz, (x != (item->attr_count-1)?",":"\r\n") );
		// add attribute value datas:
		for( x = 0; x < item->attr_count; x ++ )
		{
			cmap_str_append_bytes(resp_str, item->attrs[x].value, item->attrs[x].value_sz );
			cmap_str_append_bytes(resp_str, "\r\n", 2 );
		}
	}

	return resp_str;
}

/*
edb_quick_resp restrictions:
- One item must be defined, no more than one item may be defined (ie: no empty set responses)
- a maximum number of attributes can be defined, attributes beyond the limit will be ignored
- the va list must end in a NULL

edb_quick_resp( NULL, "something here", "key_string", "value_string", "key2_string", "value string two", NULL );
*/
cmapStruct *edb_quick_resp( mempool_t *pool, char *preamble, ... )
{
#define MAX_QUICK_ATTRS		10
	va_list ap;
	char *key;
	char *value;
	cmapStruct *items = cmap_new(pool);
	edb_net_item_t item;
	edb_net_item_attr_t attrs[MAX_QUICK_ATTRS];

	item.attr_count = 0;
	item.attrs = attrs;


	va_start( ap, preamble );
	
	while( (key = va_arg(ap, char *)) )
	{
		value = va_arg(ap, char *);
		
		if( item.attr_count >= MAX_QUICK_ATTRS )
			break;
		
		attrs[item.attr_count].key = key;
		attrs[item.attr_count].value = value;
		attrs[item.attr_count].value_sz = strlen(value);

		item.attr_count ++;
	}
	
	va_end(ap);	
	cmap_assign(items, items->num, &item);
	
	return edb_serialize_req( pool, preamble, items );
}

/* convience function converts net item to a string map */
cmapStruct *edb_net_item2strmap( mempool_t *pool, edb_net_item_t *item )
{
	int x;
	cmapStruct *sm;

	if(!item)
		return NULL;
	
	sm = cmap_new(pool);
	if(!sm)
		return NULL;
		
	for( x = 0; x < item->attr_count; x ++ )
		strmap_assign(sm, item->attrs[x].key, strlen(item->attrs[x].key), &item->attrs[x] );

	return sm;
}

void edb_handle_complete_request( ProgramContextStruct *pcs, ClientStruct *client )
{
	int i,x ;
	if( pcs->bDebugConnect )
	{
		printf("REQUEST PREAMBLE: \"%s\"\n", client->preamble );
		printf("%d item%s:\n", (int)client->item_count, (client->item_count==1?"":"s") );
		for( i = 0; i < client->item_count; i ++ )
		{
			printf("\t\t[");
			for( x = 0; x < client->items[i].attr_count; x ++ )
			{
				printf("%s:", client->items[i].attrs[x].key );
				print_len( client->items[i].attrs[x].value, client->items[i].attrs[x].value_sz );
				if( x != (client->items[i].attr_count-1) )
					printf(",");
			}
			printf("]\n");
		}
	}
#ifndef EDB_MT
	/* edb_msg_parser does all the magic of processing the request and returning the results: */
	cmapStruct *resp_items = edb_msg_parser( pcs, NULL, client->preamble, client->item_count, client->items );
	cmapStruct *resp_str;
#else
	/* start of future multithreading work */
	cmapStruct *resp_items = edb_msg_threaded_parser( pcs, NULL, client->preamble, client->item_count, client->items );
	cmapStruct *resp_str;
#endif
	if( !resp_items )
		resp_str = edb_quick_resp( NULL, EDB_RESPONSE_PA,
									EDB_RESULT_KEY, EDB_RESULT_VALUE_FAILURE,
									EDB_ERROR_KEY, EDB_ERROR_VALUE_STR_PARSE, NULL );
	else
		resp_str = edb_serialize_req( NULL, EDB_RESPONSE_PA, resp_items );

	size_t len_out = 0;
	char *str = cmap_str_join(resp_str, &len_out);
	
//	printf("reserialized_req length: %ld\n", len_out );
//	fwrite(str, 1, len_out, stdout);

	bufferevent_write( client->buf_ev, str, len_out );
	
//	mempool_describe(resp_str->mempool);

	cmap_destroy(resp_str);

	/* because we called edb_msg_parser with a NULL for the pool we need
	** to destroy the response items as well */
	if( resp_items )
		cmap_destroy(resp_items);
}

void edb_handle_invalid_req( ProgramContextStruct *pcs, ClientStruct *client )
{
	cmapStruct *resp_str;
	resp_str = edb_quick_resp( NULL, EDB_RESPONSE_PA,
								EDB_RESULT_KEY, EDB_RESULT_VALUE_FAILURE,
								EDB_ERROR_KEY, EDB_ERROR_VALUE_STR_PARSE, NULL );

	size_t len_out = 0;
	char *str = cmap_str_join(resp_str, &len_out);
	

	bufferevent_write( client->buf_ev, str, len_out );

	cmap_destroy(resp_str);
}



/* ************************************************************************************************* */
/* ************************************************************************************************* */


/**
 * Set a socket to non-blocking mode.
 */
int edb_setnonblock(int fd)
{
	int flags;

	flags = fcntl(fd, F_GETFL);
	if (flags < 0)
	        return flags;
	flags |= O_NONBLOCK;
	if (fcntl(fd, F_SETFL, flags) < 0)
	        return -1;

	return 0;
}

// Reads one "line" which is terminated by a byte containing '\0'
char *evbuffer_readline_nullterm(struct evbuffer *buffer, int *rlen)
{
	u_char *data = EVBUFFER_DATA(buffer);
	size_t len = EVBUFFER_LENGTH(buffer);
	char *line;
	unsigned int i;

	for (i = 0; i < len; i++)
	{
		if (data[i] == '\0')
			break;
	}

	if (i == len)
		return (NULL);

	if ((line = malloc(i + 1)) == NULL) {
		fprintf(stderr, "%s: out of memory\n", __func__);
		evbuffer_drain(buffer, i);
		return (NULL);
	}
	*rlen = i;
	memcpy(line, data, i);
	line[i] = '\0';

	evbuffer_drain(buffer, i + 1);

	return (line);
}

/**
 * Called by libevent when there is an error on the underlying socket
 * descriptor.
 */
void buffered_on_error(struct bufferevent *bev, short what, void *arg)
{
	ClientStruct *client = (ClientStruct *)arg;
	ProgramContextStruct *pcs = client->pcs;
	int client_was_connected;
	
	if (what & EVBUFFER_EOF)
	{
		/* Client disconnected, remove the read event and the free the client structure. */
		if( pcs->bDebugConnect )
			printf("Client disconnected.\n");
	}
	else
	{
		warn("Client socket error, disconnecting.");
		
		// buffered_on_error gets called twice in certain instances, causing errors
		/*
		Client disconnected.
		clientID 1 disconnects
		dmtdsrv: Client socket error, disconnecting.: Broken pipe
		clientID 1 disconnects
		*** glibc detected *** double free or corruption (fasttop): 0x00000000005cbd30 ***
		*/
		

	}
	cmap_lock(pcs->clients);
		client_was_connected = (cmap_remove(pcs->clients, client->cliid )!=NULL?1:0);
	cmap_unlock(pcs->clients);

	if( !client_was_connected )
	{
		if( pcs->bDebugConnect )
			printf("Client was already disconnected\n");
		return;
	}

	


	// destroy buffer event (!!):
	bufferevent_free(bev);
		
	// must close file descriptor:
//	close( client->cliid );
	mempool_destroy( &client->req_pool );
	
	// free client structure:	
	free( client );

	if( pcs->bDumpMemOnDC )
		mempool_describe( pcs->clients->mempool );

}

#define invalid_char( c ) ( c == ' ' || c == '\t' )

// line should have the format:
// <key_name> <value_data_sz>, <key2_name> <value2_data_sz>
void get_attr_info( mempool_t *pool, edb_net_item_t *cur_item, char *line )
{
	int line_len = strlen(line);
	int i;
	char *start_loc;
	int attr_i;
	cur_item->attr_count = 1;
	cur_item->cur_attr = 0;
	
	if( line_len == 0 )
	{
		cur_item->attr_count  = 0;
		return;
	}
		
	
	// tokenize line (destructively):
	for( i = 0; i < line_len; i ++ )
	{
		if( line[i] == ',' )
		{
			line[i] = '\0';
			cur_item->attr_count ++;
		}
	}

	cur_item->attrs = (edb_net_item_attr_t *)mempool_malloc_named(pool, sizeof(edb_net_item_attr_t)*cur_item->attr_count, "Item Attributes");
	memset( cur_item->attrs, 0, sizeof(edb_net_item_attr_t)*cur_item->attr_count );
	
	
	for( start_loc = line, attr_i = 0; start_loc < (line+line_len) && attr_i < cur_item->attr_count; start_loc += strlen(start_loc)+1, attr_i ++ )
	{
		edb_net_item_attr_t *cur_attr;
		while( invalid_char( *start_loc ) )
			start_loc ++;
		char *attr_name_end = strchr(start_loc, ' ');
		if(!attr_name_end)
		{
			// error malformed attribute definiton
			continue;
		}
		size_t attr_name_len = (attr_name_end - start_loc);
		size_t attr_value_len = atoi( (attr_name_end+1) );
		
		cur_attr = &cur_item->attrs[attr_i];
		
		cur_attr->key = (char *)mempool_malloc_named(pool, attr_name_len+1, "Item Attribute Key" );
		if(!cur_attr->key)
			continue;
		memcpy( cur_attr->key, start_loc, attr_name_len );
		cur_attr->key[attr_name_len] = '\0';
		
		cur_attr->value_sz = attr_value_len;
		cur_attr->filled_sz = 0;
		cur_attr->value = mempool_malloc_named(pool, attr_value_len, "Item Attribute Value" );
		
//		printf("attribute: \"" );
//		for( i = 0; i < attr_name_len; i ++ )
//			printf("%c", start_loc[i] );
//		printf("\"; attribute value length: %d bytes\n", attr_value_len );
		
	}
	
	
	

}

/**
 * Called by libevent when there is data to read.
 */
void buffered_on_read(struct bufferevent *bev, void *arg)
{
	ClientStruct *client = (ClientStruct *)arg;
	ProgramContextStruct *pcs = client->pcs;
	char *line;
/*	int line_len, i;*/
	edb_net_item_t *cur_item;
	edb_net_item_attr_t *cur_attr;
	
	/* loops around until it has handled all data,
	** this had been handled by recursively calling itself
	** but it was being called more than expected.
	*/
	for(;;)
	{
		switch( client->state )
		{
		case 0:
			if( pcs->bDumpMemOnReq )
				mempool_describe(&client->req_pool);
			
			mempool_destroy(&client->req_pool);
			mempool_init( &client->req_pool, 4096 );
			
			// READ preamble line
			line = evbuffer_readline( bev->input );
			if(!line)
				return;
			
			client->preamble = line;
			client->state ++;
			break;
		case 1:
			// READ item count
			line = evbuffer_readline( bev->input );
			if(!line)
				return;

			client->cur_item = 0;
			client->item_count = atoi( line );
			free(line);
			client->items = (edb_net_item_t *)mempool_malloc_named(&client->req_pool, sizeof(edb_net_item_t)*client->item_count, "Items");
			
			
	//printf("TO read: %d items\n", client->item_count );
			client->state ++;
			break;
		case 2:
			if( client->cur_item >= client->item_count )
			{
				edb_handle_complete_request( pcs, client );
				if( client->preamble )
				{
					free(client->preamble);
					client->preamble = NULL;
				}
				// reset state
				client->state = 0;
				break;
			}
			// READ an item's key description line
			line = evbuffer_readline( bev->input );
			if(!line)
				return;
			
			cur_item = &client->items[client->cur_item];
			get_attr_info( &client->req_pool, cur_item, line );
	//printf("attribute count: %d\n", cur_item->attr_count );
			client->state ++;
			
			free(line);
			break;
		case 3:
			cur_item = &client->items[client->cur_item];
			if( cur_item->cur_attr >= cur_item->attr_count )
			{
				// done with this item, onto the next:
				client->state --;
				client->cur_item ++;
				break;
			}

			struct evbuffer *buffer = bev->input;
			u_char *data = EVBUFFER_DATA(buffer);
			size_t len = EVBUFFER_LENGTH(buffer);		
			size_t todrain = 0;
			

			cur_attr = &cur_item->attrs[cur_item->cur_attr];
	//printf("cur_item->cur_attr: %d\n", cur_item->cur_attr );
	//printf("cur_attr->value_sz: %d\n", cur_attr->value_sz );
	//printf("cur_attr->filled_sz: %d\n", cur_attr->filled_sz );
							
			if( cur_attr->value_sz == cur_attr->filled_sz )
			{
				// got all the data, we probably need to collect a \r\n
				
				if( (len >= 1 && data[0] == '\n') )
				{
					evbuffer_drain(buffer, 1);
					cur_item->cur_attr ++;			
					break;
				}

				if( len >= 2 && data[0] == '\r' && data[1] == '\n' )
				{
					cur_item->cur_attr ++;			
					evbuffer_drain(buffer, 2 );
					break;
				}
				
				if( len > 2 )
				{
				
					printf("something is not right, we expected cr+lf, but got: \'%c\' and \'%c\'\n", data[0], data[1] );
					printf("request will be aborted.\n");
					edb_handle_invalid_req( pcs, client );
					
					if( client->preamble )
					{
						free(client->preamble);
						client->preamble = NULL;
					}
					// reset state
					client->state = 0;
				}
		
				// need moar data!
				return;
			}

			if( len == 0 )		// need more data!
				return;
				
			if( len > (cur_attr->value_sz - cur_attr->filled_sz) )
				todrain = (cur_attr->value_sz - cur_attr->filled_sz);
			else
				todrain = len;
			
			memcpy( &((char *)cur_attr->value)[cur_attr->filled_sz], data, todrain );
			evbuffer_drain(buffer, todrain);	

			cur_attr->filled_sz += todrain;
			
			if( cur_attr->value_sz == cur_attr->filled_sz )
			{
				// fall through
				break;
			}
			else
			{
				
				// wait for more data:
				return;
			}
			
			
			break;
		} /* end: switch client state */
		
	} /* end: looping forever */


}

/**
 * Called by libevent when the write buffer reaches 0.  We only
 * provide this because libevent expects it, but we don’t use it.
 */
void buffered_on_write(struct bufferevent *bev, void *arg) { }




/**
 * This function will be called by libevent when there is a connection
 * ready to be accepted.
 */
void edb_net_shell_accept(int fd, short ev, void *arg)
{
	ProgramContextStruct *pcs = (ProgramContextStruct *)arg;
	int client_fd, client_uid;
	struct sockaddr_in client_addr;
	socklen_t client_len = sizeof(client_addr);
	struct bufferevent *buf_ev;
	
	client_fd = accept(fd, (struct sockaddr *)&client_addr, &client_len);
	if (client_fd < 0)
	{
		warn("accept failed");
		return;
	}

	/* Set the client socket to non-blocking mode. */
	if (edb_setnonblock(client_fd) < 0)
		warn("failed to set client socket non-blocking");

	/* We’ve accepted a new client, create a client object. */
	ClientStruct *client = (ClientStruct *)malloc( sizeof(ClientStruct) );
	memset( client, 0, sizeof(ClientStruct) );

	mempool_init( &client->req_pool, 4096 );

	client->fd = client_fd;
	client->pcs = pcs;
	
	buf_ev = bufferevent_new(client_fd, buffered_on_read, buffered_on_write, buffered_on_error, client);

	cmap_lock(pcs->clients);
		client_uid = cmap_gethighestkey(pcs->clients)+1;
		cmap_assign(pcs->clients, client_uid, client);
	cmap_unlock(pcs->clients);

	if( pcs->bDebugConnect )
		printf("client->cliid: %d\n", client_uid );

	client->cliid = client_uid;


	// associate with the correct thread context (must be done after new and before enable, which is set and add):
	bufferevent_base_set( pcs->net_evbase, buf_ev );

	/* We have to enable it before our callbacks will be
	 * called. */
	bufferevent_enable(buf_ev, EV_READ);

	client->buf_ev = buf_ev;
	
	// onConnect
	
	
	// Do something about new connect now:
	if( pcs->bDebugConnect )	
		printf("Accepted connection from %s\n", inet_ntoa(client_addr.sin_addr));
}




